From 803cbd576f6597f4b851c6f1b7ded98691525e5a Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sun, 10 Dec 2017 01:05:37 +0100 Subject: [PATCH] dnd: Introduce gdk_drop_read_async() and use it This is the replacement for selection usage. Backend implementations for X11 (missing support for backwards compat formats like COMPOUND_TEXT) and Wayland are included. GTK code should be adapted to use gdk_drop_read_*() functions instead of gtk_drag_get_data(). --- gdk/gdkdnd.c | 80 +++++++++++++++++++ gdk/gdkdnd.h | 13 ++++ gdk/gdkdndprivate.h | 10 +++ gdk/wayland/gdkdnd-wayland.c | 81 ++++++++++++++++++++ gdk/x11/gdkclipboard-x11.c | 2 +- gdk/x11/gdkclipboard-x11.h | 1 + gdk/x11/gdkdnd-x11.c | 144 +++++++++++++++++++++++++++++++++-- gtk/gtkdnd.c | 137 +++++++++++++++++++++++++++++---- 8 files changed, 447 insertions(+), 21 deletions(-) diff --git a/gdk/gdkdnd.c b/gdk/gdkdnd.c index ead7bdfdb1..577ff730ed 100644 --- a/gdk/gdkdnd.c +++ b/gdk/gdkdnd.c @@ -309,6 +309,40 @@ gdk_drag_context_finalize (GObject *object) G_OBJECT_CLASS (gdk_drag_context_parent_class)->finalize (object); } +static void +gdk_drag_context_read_local_async (GdkDragContext *context, + GdkContentFormats *formats, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (context, cancellable, callback, user_data); + g_task_set_priority (task, io_priority); + g_task_set_source_tag (task, gdk_drag_context_read_local_async); + + g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Reading not implemented.")); + g_object_unref (task); +} + +static GInputStream * +gdk_drag_context_read_local_finish (GdkDragContext *context, + const char **out_mime_type, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, context), NULL); + g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gdk_drag_context_read_local_async, NULL); + + if (out_mime_type) + *out_mime_type = g_task_get_task_data (G_TASK (result)); + + return g_task_propagate_pointer (G_TASK (result), error); +} + static void gdk_drag_context_class_init (GdkDragContextClass *klass) { @@ -637,6 +671,52 @@ gdk_drag_get_selection (GdkDragContext *context) return GDK_DRAG_CONTEXT_GET_CLASS (context)->get_selection (context); } +void +gdk_drop_read_async (GdkDragContext *context, + const char **mime_types, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GdkContentFormats *formats; + + g_return_if_fail (GDK_IS_DRAG_CONTEXT (context)); + g_return_if_fail (mime_types != NULL && mime_types[0] != NULL); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + g_return_if_fail (callback != NULL); + + formats = gdk_content_formats_new (mime_types, g_strv_length ((char **) mime_types)); + + GDK_DRAG_CONTEXT_GET_CLASS (context)->read_async (context, + formats, + io_priority, + cancellable, + callback, + user_data); + + gdk_content_formats_unref (formats); +} + +GInputStream * +gdk_drop_read_finish (GdkDragContext *context, + const char **out_mime_type, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (GDK_IS_DRAG_CONTEXT (context), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + if (g_async_result_is_tagged (result, gdk_drag_context_read_local_async)) + { + return gdk_drag_context_read_local_finish (context, out_mime_type, result, error); + } + else + { + return GDK_DRAG_CONTEXT_GET_CLASS (context)->read_finish (context, out_mime_type, result, error); + } +} + /** * gdk_drag_context_get_drag_window: * @context: a #GdkDragContext diff --git a/gdk/gdkdnd.h b/gdk/gdkdnd.h index 85f8fa0a38..70c69e1267 100644 --- a/gdk/gdkdnd.h +++ b/gdk/gdkdnd.h @@ -121,6 +121,19 @@ void gdk_drop_finish (GdkDragContext *context, GDK_AVAILABLE_IN_ALL GdkAtom gdk_drag_get_selection (GdkDragContext *context); +GDK_AVAILABLE_IN_3_94 +void gdk_drop_read_async (GdkDragContext *context, + const char **mime_types, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GDK_AVAILABLE_IN_3_94 +GInputStream * gdk_drop_read_finish (GdkDragContext *context, + const char **out_mime_type, + GAsyncResult *result, + GError **error); + /* Source side */ GDK_AVAILABLE_IN_ALL diff --git a/gdk/gdkdndprivate.h b/gdk/gdkdndprivate.h index 78ae19b4e3..e44e49bfe8 100644 --- a/gdk/gdkdndprivate.h +++ b/gdk/gdkdndprivate.h @@ -87,6 +87,16 @@ struct _GdkDragContextClass { void (*drop_finish) (GdkDragContext *context, gboolean success, guint32 time_); + void (* read_async) (GdkDragContext *context, + GdkContentFormats *formats, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + GInputStream * (* read_finish) (GdkDragContext *context, + const char **out_mime_type, + GAsyncResult *result, + GError **error); gboolean (*drop_status) (GdkDragContext *context); GdkWindow* (*get_drag_window) (GdkDragContext *context); void (*set_hotspot) (GdkDragContext *context, diff --git a/gdk/wayland/gdkdnd-wayland.c b/gdk/wayland/gdkdnd-wayland.c index a7281b82cc..b64f487738 100644 --- a/gdk/wayland/gdkdnd-wayland.c +++ b/gdk/wayland/gdkdnd-wayland.c @@ -24,10 +24,14 @@ #include "gdkprivate-wayland.h" #include "gdkcontentformats.h" #include "gdkdisplay-wayland.h" +#include "gdkintl.h" #include "gdkseat-wayland.h" #include "gdkdeviceprivate.h" +#include +#include +#include #include #define GDK_TYPE_WAYLAND_DRAG_CONTEXT (gdk_wayland_drag_context_get_type ()) @@ -324,6 +328,80 @@ gdk_wayland_drag_context_drop_finish (GdkDragContext *context, gdk_wayland_selection_set_offer (display, selection, NULL); } +static void +gdk_wayland_drag_context_read_async (GdkDragContext *context, + GdkContentFormats *formats, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GdkDisplay *display; + GdkContentFormats *dnd_formats; + GInputStream *stream; + struct wl_data_offer *offer; + const char *mime_type; + int pipe_fd[2]; + GError *error = NULL; + GTask *task; + + display = gdk_drag_context_get_display (context), + task = g_task_new (context, cancellable, callback, user_data); + g_task_set_priority (task, io_priority); + g_task_set_source_tag (task, gdk_wayland_drag_context_read_async); + + GDK_NOTE (DND, char *s = gdk_content_formats_to_string (formats); + g_printerr ("%p: read for %s\n", context, s); + g_free (s); ); + dnd_formats = gdk_wayland_selection_get_targets (display, + gdk_drag_get_selection (context)); + mime_type = gdk_content_formats_match_mime_type (formats, dnd_formats); + if (mime_type == NULL) + { + g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("No compatible transfer format found")); + return; + } + /* offer formats should be empty if we have no offer */ + offer = gdk_wayland_selection_get_offer (display, + gdk_drag_get_selection (context)); + g_assert (offer); + + g_task_set_task_data (task, (gpointer) mime_type, NULL); + + if (!g_unix_open_pipe (pipe_fd, FD_CLOEXEC, &error)) + { + g_task_return_error (task, error); + return; + } + + wl_data_offer_accept (offer, + _gdk_wayland_display_get_serial (GDK_WAYLAND_DISPLAY (display)), + mime_type); + wl_data_offer_receive (offer, mime_type, pipe_fd[1]); + stream = g_unix_input_stream_new (pipe_fd[0], TRUE); + close (pipe_fd[1]); + g_task_return_pointer (task, stream, g_object_unref); +} + +static GInputStream * +gdk_wayland_drag_context_read_finish (GdkDragContext *context, + const char **out_mime_type, + GAsyncResult *result, + GError **error) +{ + GTask *task; + + g_return_val_if_fail (g_task_is_valid (result, G_OBJECT (context)), NULL); + task = G_TASK (result); + g_return_val_if_fail (g_task_get_source_tag (task) == gdk_wayland_drag_context_read_async, NULL); + + if (out_mime_type) + *out_mime_type = g_task_get_task_data (task); + + return g_task_propagate_pointer (task, error); +} + static gboolean gdk_wayland_drag_context_drop_status (GdkDragContext *context) { @@ -471,6 +549,9 @@ gdk_wayland_drag_context_class_init (GdkWaylandDragContextClass *klass) context_class->drag_drop = gdk_wayland_drag_context_drag_drop; context_class->drop_reply = gdk_wayland_drag_context_drop_reply; context_class->drop_finish = gdk_wayland_drag_context_drop_finish; + context_class->drop_finish = gdk_wayland_drag_context_drop_finish; + context_class->read_async = gdk_wayland_drag_context_read_async; + context_class->read_finish = gdk_wayland_drag_context_read_finish; context_class->drop_status = gdk_wayland_drag_context_drop_status; context_class->get_selection = gdk_wayland_drag_context_get_selection; context_class->get_drag_window = gdk_wayland_drag_context_get_drag_window; diff --git a/gdk/x11/gdkclipboard-x11.c b/gdk/x11/gdkclipboard-x11.c index f6d7b8cc63..817792e5a1 100644 --- a/gdk/x11/gdkclipboard-x11.c +++ b/gdk/x11/gdkclipboard-x11.c @@ -287,7 +287,7 @@ static const struct { { "SAVE_TARGETS", NULL, NULL, "NULL", 32, handle_save_targets } }; -static GSList * +GSList * gdk_x11_clipboard_formats_to_targets (GdkContentFormats *formats) { GSList *targets; diff --git a/gdk/x11/gdkclipboard-x11.h b/gdk/x11/gdkclipboard-x11.h index a4ed9d44a4..0c0322dbc6 100644 --- a/gdk/x11/gdkclipboard-x11.h +++ b/gdk/x11/gdkclipboard-x11.h @@ -36,6 +36,7 @@ GType gdk_x11_clipboard_get_type (void) G_GNUC_CO GdkClipboard * gdk_x11_clipboard_new (GdkDisplay *display, const gchar *selection); +GSList * gdk_x11_clipboard_formats_to_targets (GdkContentFormats *formats); G_END_DECLS diff --git a/gdk/x11/gdkdnd-x11.c b/gdk/x11/gdkdnd-x11.c index 85a98c5053..9b626e2ad1 100644 --- a/gdk/x11/gdkdnd-x11.c +++ b/gdk/x11/gdkdnd-x11.c @@ -25,16 +25,19 @@ #include "config.h" #include "gdkx11dnd.h" -#include "gdkdndprivate.h" -#include "gdkdeviceprivate.h" -#include "gdkinternals.h" #include "gdkasync.h" -#include "gdkcontentformatsprivate.h" +#include "gdkclipboardprivate.h" +#include "gdkclipboard-x11.h" +#include "gdkdeviceprivate.h" +#include "gdkdisplay-x11.h" +#include "gdkdndprivate.h" +#include "gdkinternals.h" +#include "gdkintl.h" #include "gdkproperty.h" #include "gdkprivate-x11.h" #include "gdkscreen-x11.h" -#include "gdkdisplay-x11.h" +#include "gdkselectioninputstream-x11.h" #include #include @@ -242,6 +245,135 @@ static void gdk_x11_drag_context_cancel (GdkDragContext *con static void gdk_x11_drag_context_drop_performed (GdkDragContext *context, guint32 time); +static void +gdk_x11_drag_context_read_got_stream (GObject *source, + GAsyncResult *res, + gpointer data) +{ + GTask *task = data; + GError *error = NULL; + GInputStream *stream; + const char *type; + int format; + + stream = gdk_x11_selection_input_stream_new_finish (res, &type, &format, &error); + if (stream == NULL) + { + GSList *targets, *next; + + targets = g_task_get_task_data (task); + next = targets->next; + if (next) + { + GdkDragContext *context = GDK_DRAG_CONTEXT (g_task_get_source_object (task)); + + GDK_NOTE (DND, g_printerr ("reading %s failed, trying %s next\n", + (char *) targets->data, (char *) next->data)); + targets->next = NULL; + g_task_set_task_data (task, next, (GDestroyNotify) g_slist_free); + gdk_x11_selection_input_stream_new_async (gdk_drag_context_get_display (context), + gdk_drag_get_selection (context), + next->data, + CurrentTime, + g_task_get_priority (task), + g_task_get_cancellable (task), + gdk_x11_drag_context_read_got_stream, + task); + g_error_free (error); + return; + } + + g_task_return_error (task, error); + } + else + { + const char *mime_type = ((GSList *) g_task_get_task_data (task))->data; +#if 0 + gsize i; + + for (i = 0; i < G_N_ELEMENTS (special_targets); i++) + { + if (g_str_equal (mime_type, special_targets[i].x_target)) + { + g_assert (special_targets[i].mime_type != NULL); + + GDK_NOTE(CLIPBOARD, g_printerr ("%s: reading with converter from %s to %s\n", + cb->selection, mime_type, special_targets[i].mime_type)); + mime_type = g_intern_string (special_targets[i].mime_type); + g_task_set_task_data (task, g_slist_prepend (NULL, (gpointer) mime_type), (GDestroyNotify) g_slist_free); + stream = special_targets[i].convert (cb, stream, type, format); + break; + } + } +#endif + + GDK_NOTE(DND, g_printerr ("reading DND as %s now\n", + mime_type)); + g_task_return_pointer (task, stream, g_object_unref); + } + + g_object_unref (task); +} + +static void +gdk_x11_drag_context_read_async (GdkDragContext *context, + GdkContentFormats *formats, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSList *targets; + GTask *task; + + task = g_task_new (context, cancellable, callback, user_data); + g_task_set_priority (task, io_priority); + g_task_set_source_tag (task, gdk_x11_drag_context_read_async); + + targets = gdk_x11_clipboard_formats_to_targets (formats); + g_task_set_task_data (task, targets, (GDestroyNotify) g_slist_free); + if (targets == NULL) + { + g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("No compatible transfer format found")); + return; + } + + GDK_NOTE(DND, g_printerr ("new read for %s (%u other options)\n", + (char *) targets->data, g_slist_length (targets->next))); + gdk_x11_selection_input_stream_new_async (gdk_drag_context_get_display (context), + gdk_drag_get_selection (context), + targets->data, + CurrentTime, + io_priority, + cancellable, + gdk_x11_drag_context_read_got_stream, + task); +} + +static GInputStream * +gdk_x11_drag_context_read_finish (GdkDragContext *context, + const char **out_mime_type, + GAsyncResult *result, + GError **error) +{ + GTask *task; + + g_return_val_if_fail (g_task_is_valid (result, G_OBJECT (context)), NULL); + task = G_TASK (result); + g_return_val_if_fail (g_task_get_source_tag (task) == gdk_x11_drag_context_read_async, NULL); + + if (out_mime_type) + { + GSList *targets; + + targets = g_task_get_task_data (task); + *out_mime_type = targets ? targets->data : NULL; + } + + return g_task_propagate_pointer (task, error); +} + static void gdk_x11_drag_context_class_init (GdkX11DragContextClass *klass) { @@ -258,6 +390,8 @@ gdk_x11_drag_context_class_init (GdkX11DragContextClass *klass) context_class->drop_reply = gdk_x11_drag_context_drop_reply; context_class->drop_finish = gdk_x11_drag_context_drop_finish; context_class->drop_status = gdk_x11_drag_context_drop_status; + context_class->read_async = gdk_x11_drag_context_read_async; + context_class->read_finish = gdk_x11_drag_context_read_finish; context_class->get_selection = gdk_x11_drag_context_get_selection; context_class->get_drag_window = gdk_x11_drag_context_get_drag_window; context_class->set_hotspot = gdk_x11_drag_context_set_hotspot; diff --git a/gtk/gtkdnd.c b/gtk/gtkdnd.c index 1526a3fc6b..fd28212b0c 100644 --- a/gtk/gtkdnd.c +++ b/gtk/gtkdnd.c @@ -326,6 +326,115 @@ gtk_drag_get_event_actions (const GdkEvent *event, * Destination side * ********************/ +typedef struct { + GdkDragContext *context; + GtkWidget *widget; + const char *mime_type; + guint time; +} GtkDragGetData; + +static void +gtk_drag_get_data_finish (GtkDragGetData *data, + guchar *bytes, + gsize size) +{ + GtkDragDestSite *site; + GtkSelectionData sdata; + + site = g_object_get_data (G_OBJECT (data->widget), "gtk-drag-dest"); + + sdata.selection = gdk_drag_get_selection (data->context); + sdata.target = data->mime_type; + sdata.type = data->mime_type; + sdata.format = 8; + sdata.length = size; + sdata.data = bytes; + sdata.display = gtk_widget_get_display (data->widget); + + if (site && site->target_list) + { + if (gdk_content_formats_contain_mime_type (site->target_list, data->mime_type)) + { + if (!(site->flags & GTK_DEST_DEFAULT_DROP) || + size >= 0) + g_signal_emit_by_name (data->widget, + "drag-data-received", + data->context, + &sdata, + data->time); + } + } + else + { + g_signal_emit_by_name (data->widget, + "drag-data-received", + data->context, + &sdata, + data->time); + } + + if (site && site->flags & GTK_DEST_DEFAULT_DROP) + { + + gtk_drag_finish (data->context, + size > 0, + (gdk_drag_context_get_selected_action (data->context) == GDK_ACTION_MOVE), + data->time); + } + + g_object_unref (data->widget); + g_slice_free (GtkDragGetData, data); +} + +static void +gtk_drag_get_data_got_data (GObject *source, + GAsyncResult *result, + gpointer data) +{ + gssize written; + + written = g_output_stream_splice_finish (G_OUTPUT_STREAM (source), result, NULL); + if (written < 0) + { + gtk_drag_get_data_finish (data, NULL, 0); + } + else + { + gtk_drag_get_data_finish (data, + g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (source)), + g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (source))); + } +} + +static void +gtk_drag_get_data_got_stream (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GtkDragGetData *data = user_data; + GInputStream *input_stream; + GOutputStream *output_stream; + + input_stream = gdk_drop_read_finish (GDK_DRAG_CONTEXT (source), &data->mime_type, result, NULL); + if (input_stream == NULL) + { + gtk_drag_get_data_finish (data, NULL, 0); + return; + } + + output_stream = g_memory_output_stream_new_resizable (); + g_output_stream_splice_async (output_stream, + input_stream, + G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, + G_PRIORITY_DEFAULT, + NULL, + gtk_drag_get_data_got_data, + data); + g_object_unref (output_stream); + g_object_unref (input_stream); + +} + /** * gtk_drag_get_data: (method) * @widget: the widget that will receive the @@ -351,25 +460,23 @@ gtk_drag_get_data (GtkWidget *widget, GdkAtom target, guint32 time_) { - GtkWidget *selection_widget; + GtkDragGetData *data; g_return_if_fail (GTK_IS_WIDGET (widget)); g_return_if_fail (GDK_IS_DRAG_CONTEXT (context)); - selection_widget = gtk_drag_get_ipc_widget (widget); - - g_object_ref (context); - g_object_ref (widget); - - g_signal_connect (selection_widget, "selection-received", - G_CALLBACK (gtk_drag_selection_received), widget); - - g_object_set_data (G_OBJECT (selection_widget), I_("drag-context"), context); - - gtk_selection_convert (selection_widget, - gdk_drag_get_selection (context), - target, - time_); + data = g_slice_new0 (GtkDragGetData); + data->widget = g_object_ref (widget); + data->context = context; + data->mime_type = target; + data->time = time_; + + gdk_drop_read_async (context, + (const gchar *[2]) { target, NULL }, + G_PRIORITY_DEFAULT, + NULL, + gtk_drag_get_data_got_stream, + data); } /** -- 2.30.2